package cn.geminis.lowcode.processor;

import cn.geminis.lowcode.annotation.QueryFilter;
import cn.geminis.lowcode.annotation.QueryFilterProperties;
import cn.geminis.lowcode.annotation.QueryFilterProperty;
import cn.geminis.lowcode.compiler.Context;
import cn.geminis.lowcode.compiler.code.*;
import cn.geminis.lowcode.utils.Flags;
import cn.geminis.lowcode.utils.Utils;

import javax.lang.model.element.Element;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @author puddi
 */
public class QueryFilterProcessor implements BaseProcessor {

    @Override
    public Class<? extends Annotation> getSupportAnnotation() {
        return QueryFilter.class;
    }

    @Override
    public void process(Element element) {
        var annotation = element.getAnnotation(QueryFilter.class);
        var packageName = Utils.getElementPackageName(element);
        var filterPackageName = packageName + ".filter";
        var filterName = element.getSimpleName() + "Filter";

        LcClass filter;
        var existFilter = Context.INSTANCE.getElementUtils().getTypeElement(filterPackageName + "." + filterName);
        var createClass = Objects.isNull(existFilter);
        if (createClass) {
            var entityName = packageName + "." + element.getSimpleName();

            filter = annotation.jpa() ?
                    new LcClass(
                            LcPackage.ensure(filterPackageName),
                            new LcModifier(
                                    Flags.PUBLIC,
                                    new LcAnnotation("lombok.Getter"),
                                    new LcAnnotation("lombok.Setter"),
                                    new LcAnnotation("lombok.Builder")
                            ),
                            filterName,
                            "org.springframework.data.jpa.domain.Specification<" + entityName + ">"
                    )
                    :
                    new LcClass(
                            LcPackage.ensure(filterPackageName),
                            new LcModifier(
                                    Flags.PUBLIC,
                                    new LcAnnotation("lombok.Getter"),
                                    new LcAnnotation("lombok.Setter"),
                                    new LcAnnotation("lombok.Builder")
                            ),
                            filterName
                    );

            if (annotation.jpa()) {
                filter.append(new LcVariable(
                        new LcModifier(Flags.PRIVATE),
                        "String[]",
                        "include"
                ));
            }

            var entityClass = new LcClass(element);
            var queryFields = entityClass.getFields().stream()
                    .filter(field -> field.existAnnotation("cn.geminis.lowcode.annotation.QueryFilterProperty")
                            || field.existAnnotation("cn.geminis.lowcode.annotation.QueryFilterProperties"))
                    .toList();
            this.addFields(filter, queryFields);
            if (annotation.jpa()) {
                this.addMethod(filter, entityName, queryFields);
            }

            filter.generateSourceFile();
            Utils.noteMessage(filterPackageName + "." + filterName + " Generated");
        }
    }

    private String propertyName(LcVariable field, QueryFilterProperty annotation) {
        if ("".equals(annotation.propertyName())) {
            if (annotation.filterType().equals(QueryFilterProperty.FilterType.Equal)) {
                return field.getName();
            } else {
                return field.getName() + annotation.filterType();
            }
        } else {
            return annotation.propertyName();
        }
    }

    private String propertyType(LcVariable field, QueryFilterProperty annotation) {
        if (
                !Objects.isNull(annotation) && (
                        annotation.filterType().equals(QueryFilterProperty.FilterType.IsEmpty) ||
                                annotation.filterType().equals(QueryFilterProperty.FilterType.IsNotEmpty) ||
                                annotation.filterType().equals(QueryFilterProperty.FilterType.IsNull) ||
                                annotation.filterType().equals(QueryFilterProperty.FilterType.IsNotNull)
                )
        ) {
            return "Boolean";
        }

        var type = Objects.isNull(annotation) || "".equals(annotation.propertyType()) ?
                switch (field.getType()) {
                    case "int" -> "Integer";
                    case "long" -> "Long";
                    case "double" -> "Double";
                    case "byte" -> "Byte";
                    case "float" -> "Float";
                    case "boolean" -> "Boolean";
                    default -> field.getType();
                }
                :
                annotation.propertyType();

        if (!Objects.isNull(annotation) && annotation.filterType().equals(QueryFilterProperty.FilterType.In)) {
            type += "[]";
        }

        return type;
    }

    private void addFields(LcClass filter, List<LcVariable> queryFields) {
        queryFields.forEach(field -> {
            var annotationsMulti = field.getAnnotation(QueryFilterProperties.class);
            var annotations = annotationsMulti.isEmpty() ?
                    field.getAnnotation(QueryFilterProperty.class)
                    : Arrays.stream(annotationsMulti.get(0).value()).toList();

            if (annotations.isEmpty()) {
                //为空时，是低代码添加的内容
                filter.append(new LcVariable(
                        new LcModifier(Flags.PRIVATE),
                        propertyType(field, null),
                        field.getName()
                ));
            } else {
                annotations.forEach(annotation ->
                        filter.append(new LcVariable(
                                new LcModifier(Flags.PRIVATE),
                                propertyType(field, annotation),
                                propertyName(field, annotation)
                        ))
                );
            }
        });
    }

    private void addMethod(LcClass filter, String entityName, List<LcVariable> queryFields) {
        var body = new ArrayList<AbstractStatement<?>>();
        body.add(
                new LcVariable(
                        new LcModifier(Flags.PARAMETER),
                        "cn.geminis.data.jpa.QueryPredicateBuilder<" + entityName + ">",
                        "builder",
                        new LcNewClass(
                                "cn.geminis.data.jpa.QueryPredicateBuilder",
                                List.of("root", "query", "criteriaBuilder")
                        ).getJdkTree(),
                        true
                )
        );
        body.add(
                new LcIf(
                        new LcUnary(
                                "NOT",
                                new LcApply(
                                        List.of(
                                                new LcApply.InvokeInfo("query.getResultType"),
                                                new LcApply.InvokeInfo("getName"),
                                                new LcApply.InvokeInfo("equals", new LcLiteral("java.lang.Long"))
                                        )
                                )
                        ),
                        List.of(
                                new LcApply(
                                        "builder.include",
                                        new LcIdent("include")
                                ).exec()
                        )
                )
        );

        queryFields.forEach(field -> {
            var annotationsMulti = field.getAnnotation(QueryFilterProperties.class);
            var annotations = annotationsMulti.isEmpty() ?
                    field.getAnnotation(QueryFilterProperty.class)
                    : Arrays.stream(annotationsMulti.get(0).value()).toList();
            if (annotations.isEmpty()) {
                body.add(
                        new LcApply(
                                "builder.equal",
                                new LcLiteral(field.getName()),
                                new LcIdent(field.getName())
                        ).exec()
                );
            } else {
                annotations.forEach(annotation ->
                        body.add(
                                "".equals(annotation.valueFormat()) ?
                                        new LcApply(
                                                "builder." + Utils.lowerFirst(annotation.filterType().toString()),
                                                new LcLiteral("".equals(annotation.fieldName()) ? field.getName() : annotation.fieldName()),
                                                new LcIdent(propertyName(field, annotation))

                                        ).exec() :
                                        new LcApply(
                                                "builder." + Utils.lowerFirst(annotation.filterType().toString()),
                                                new LcLiteral("".equals(annotation.fieldName()) ? field.getName() : annotation.fieldName()),
                                                new LcIdent(propertyName(field, annotation)),
                                                new LcLiteral(annotation.valueFormat())
                                        ).exec()
                        )
                );
            }
        });

        body.add(
                new LcApply(
                        "builder.build"
                ).rtn()
        );


        filter.append(new LcMethod(
                new LcModifier(Flags.PUBLIC),
                "jakarta.persistence.criteria.Predicate",
                "toPredicate",
                List.of(
                        new LcVariable("jakarta.persistence.criteria.Root<" + entityName + ">", "root"),
                        new LcVariable("jakarta.persistence.criteria.CriteriaQuery<?>", "query"),
                        new LcVariable("jakarta.persistence.criteria.CriteriaBuilder", "criteriaBuilder")
                ),
                List.of(),
                body
        ));
    }
}
